/*
 * Copyright (c) 2013-2018 上海汇数数据. All rights reserved.
 * @(#) CipherApi.java 2018-08-06 17:26
 */

package cn.jh.common.core.crypto;

import com.google.common.base.Charsets;
import org.jetbrains.annotations.Contract;

import java.nio.charset.Charset;
import java.security.Key;
import java.util.Objects;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkArgument;


/**
 * 密钥算法接口。
 *
 * @author Fuchun
 * @since 1.0
 */
public interface CipherApi {

    /** 加密算法名称。 */
    String getAlgorithm();

    /** 算法提供者 (实现者 SunJCE, BC等) 名称。*/
    String getProvider();

    /**
     * 根据实现类的指定算法加密指定的数据。
     *
     * @param plaintext 要加密的数据。
     * @param key 二进制的密钥。
     * @return 返回加密后的数据。
     * @throws CryptoException 如果加密过程中发生错误。
     */
    byte[] encrypt(byte[] plaintext, byte[] key) throws CryptoException;

    /**
     * 根据实现类的指定算法加密指定的数据。
     *
     * @param plaintext 要加密的数据。
     * @param key 加密的密钥。
     * @return 返回加密后的数据。
     * @throws CryptoException 如果加密过程中发生错误。
     */
    byte[] encrypt(byte[] plaintext, Key key) throws CryptoException;

    /**
     * 根据实现类的指定算法加密字符串。
     *
     * @param plaintext 要加密的字符串。
     * @param key 加密的密钥。
     * @param charset 加密的字符串的字符集，默认：`UTF-8`。
     * @return 返回加密后的数据。
     * @throws CryptoException 如果加密过程中发生错误。
     */
    @Contract("null, _, _ -> null")
    default byte[] encryptString(String plaintext, byte[] key, Charset charset) throws CryptoException {
        if (plaintext == null) return null;
        checkArgument(key != null && key.length > 0, "The key is invalid.");
        Charset c = firstNonNull(charset, Charsets.UTF_8);
        return encrypt(plaintext.getBytes(c), key);
    }

    @Contract("null, _, _ -> null")
    default byte[] encryptString(String plaintext, Key key, Charset charset) throws CryptoException {
        if (plaintext == null) return null;
        checkArgument(key != null &&
                Objects.equals(getAlgorithm(), key.getAlgorithm()), "The key is invalid.");
        Charset c = firstNonNull(charset, Charsets.UTF_8);
        return encrypt(plaintext.getBytes(c), key);
    }

    /**
     * 根据实现类的指定算法解密指定的数据。
     *
     * @param cipherText 密文数据。
     * @param key 二进制的密钥。
     * @return 返回解密后的数据。
     * @throws CryptoException 如果解密过程中发生错误。
     */
    byte[] decrypt(byte[] cipherText, byte[] key) throws CryptoException;

    /**
     * 根据实现类的指定算法解密指定的数据。
     *
     * @param cipherText 密文数据。
     * @param key 解密的密钥。
     * @return 返回解密后的数据。
     * @throws CryptoException 如果解密过程中发生错误。
     */
    byte[] decrypt(byte[] cipherText, Key key) throws CryptoException;

    /**
     * 根据实现类的指定算法解密指定的数据。
     *
     * @param encrypted 密文数据。
     * @param key 解密的密钥。
     * @param charset 解密后输出的字符串的字符集，默认：`UTF-8`。
     * @return 返回解密后的字符串。
     * @throws CryptoException 如果解密过程中发生错误。
     */
    default String decryptToString(byte[] encrypted, byte[] key, Charset charset) throws CryptoException {
        if (encrypted == null) return null;
        if (encrypted.length == 0) return "";
        byte[] plain = decrypt(encrypted, key);
        if (plain.length == 0) return "";
        Charset c = firstNonNull(charset, Charsets.UTF_8);
        return new String(plain, c);
    }

    @Contract("!null, _, _ -> !null")
    default String decryptToString(byte[] encrypted, Key key, Charset charset) throws CryptoException {
        if (encrypted == null) return null;
        if (encrypted.length == 0) return "";
        byte[] plain = decrypt(encrypted, key);
        if (plain.length == 0) return "";
        return new String(plain, firstNonNull(charset, Charsets.UTF_8));
    }
}
